package de.hsb.ai.agent.misterx.pathfinding;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import de.hsb.ai.agent.misterx.Drawer;
import de.hsb.ai.agent.misterx.brain.Brain;
import de.hsb.ai.agent.misterx.pathheuristic.INodeHeuristic;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;


public class Pathfinding {
	
	private List<INodeHeuristic> _nodeHeuristics = new ArrayList<INodeHeuristic>();
	
	
	public Pathfinding() {
		
	}
	
	/**
	 * 
	 * @param nodeAnalyser
	 */
	public void addNodeHeuristic(INodeHeuristic nodeAnalyser) {
		this._nodeHeuristics.add(nodeAnalyser);
	}
	
	/**
	 * 
	 * @param nodeAnalyser
	 */
	public void removeNodeHeuristic(INodeHeuristic nodeAnalyser) {
		this._nodeHeuristics.remove(nodeAnalyser);
	}
	
	/**
	 * 
	 * @param endNodeIndex
	 * @param startNodeIndex
	 * @return
	 */
	public List<Node> getPath(int endNodeIndex, int startNodeIndex) {
		return this.getPath(endNodeIndex, startNodeIndex, this._nodeHeuristics);
	}
	
	/**
	 * 
	 * @param endNodeIndex
	 * @param startNodeIndex
	 * @param nodeHeuristic
	 * @return
	 */
	public List<Node> getPath(int endNodeIndex, int startNodeIndex, INodeHeuristic nodeHeuristic) {
		List<INodeHeuristic> nodeHeuristics = new ArrayList<INodeHeuristic>();
		nodeHeuristics.add(nodeHeuristic);
		return getPath(endNodeIndex, startNodeIndex, nodeHeuristics);
	}
	
	/**
	 * 
	 * @param endNodeIndex
	 * @param startNodeIndex
	 * @param nodeHeuristics
	 * @return
	 */
	public List<Node> getPath(int endNodeIndex, int startNodeIndex, List<INodeHeuristic> nodeHeuristics) {
		for(INodeHeuristic nodeAnalyser : nodeHeuristics) {
			nodeAnalyser.update();
		}
		List<Node> openList = new ArrayList<Node>();
		List<Node> closedList = new ArrayList<Node>();
		
		Node startNode = new Node(startNodeIndex, null);
		startNode.setPosition(Brain.GEOANALYSER.getNodePosition(startNodeIndex));
		Node endNode = new Node(endNodeIndex, null);
		endNode.setPosition(Brain.GEOANALYSER.getNodePosition(endNodeIndex));
		
		//setze den StartNode als Start.
		openList.add(startNode);

		while(openList.size() != 0) {
			//hole dir den Node mit den niedrigsten WegKosten und selectiere ihn als zu untersuchenen Node.
			Node currentNode = this.getLowestCostNode(openList);
			if(currentNode != null) {
				//entferne den untersuchenen Node von der Openlist.
				openList.remove(currentNode);
				
				//tue ihn in die CloseList, damit er als untersucht gilt.
				closedList.add(currentNode);
				
				//hole dir alle (begehbare)Nachbarn ein.
				List<Node> neighbourNodes = this.getWalkableNodes(currentNode);
				
//////////////////////////NACHBARN/////////////////////////
				//gehe alle Nachbarn durch, die noch nicht in der CloseList sich befinden.
				for(int i = 0; i < neighbourNodes.size(); i++) {				
					Node testNode = neighbourNodes.get(i);
					Node onCloseListNode = this.isOnList(testNode, closedList);
					
					if(onCloseListNode == null) {
						//////////////////////ABHANDELN DER ZU PRÜFENEN NACHBARN//////////////////////////
						Node onOpenListNode = this.isOnList(testNode, openList);
						
						int G = currentNode._G + 10; //Wegkosten vom Start bis zum derzeitigen Node.
						
						int H = 0;
						for(INodeHeuristic nodeAnalyser : nodeHeuristics) {
							H += nodeAnalyser.getHeuristik(startNode, endNode, testNode); //weitere Heuristiken
						}
						
						int F = G + H; //bilde aus den beiden Kosten eine Summe.
						//System.out.println(testNode + " :_> " + H);
						
						//wenn der Node nooch nicht der Openlist(noch zu untersuchende Nodes) hinzufegügt wurde, füge ihn hinzu.
						if(onOpenListNode == null) {
							openList.add(testNode);
								 
							testNode._parent = currentNode;
							testNode._G = G;
							testNode._H = H;
							testNode._F = F;
								
						} else { //wenn der Node schon in der Openlist vorhanden ist, schaue ob die Kosten des gerade überprüften Nachbarnode geringer ist.
							if(G < onOpenListNode._G) { //HIER WAR DER FEHLER!!!!
								onOpenListNode._parent = currentNode;
								onOpenListNode._H = H;
								onOpenListNode._G = G;
								onOpenListNode._F = F;
							}
						}
						////////////////////////////////////////////////////////////////////////////////////
					}
				}
/////////////////////////////////////////////////////////////
				//Brech ab wenn die Openliste leer ist. Heisst das kein Weg gefunden werden konnte.
				if(openList.size() == 0) {
					Drawer.addNodeHeuristics(closedList);
					return null;
				}
				
				//Brech ab wenn der Endnode gefunden wurde und somit das Ziel erreicht.
				if(currentNode._index == endNode._index) {
					Drawer.addNodeHeuristics(closedList);
					
					endNode = currentNode;
					//ermittle durch rückwärts durchiterieren der parents bis hin zum startKnoten (parent == null).
					return this.getBackTracePath(endNode);
				}
			}
		}
		return null;
	}
	
	/**
	 * 
	 * @param currentNode
	 * @return
	 */
	private List<Node> getWalkableNodes(Node currentNode) {
		List<Node> nodes = new ArrayList<Node>();
		
		Set<ScotlandJadeEdge> scotlandJadeEdges = Brain.SCOTLANDJADEMAP.edgesOf(Brain.SCOTLANDJADEMAP.get(currentNode._index));
		
		//setzt scotlandJadeEdge Struktur in neue brauchbare für Pathfinding um.
		for(ScotlandJadeEdge scotlandJadeEdge : scotlandJadeEdges) {
			
			int nodeIndex = scotlandJadeEdge.getTargetIndex(currentNode._index);
			//System.out.println("FROM " + currentNode._index + " TO " + nodeIndex);
			Node node = new Node(nodeIndex, currentNode);
			node.setPosition(Brain.GEOANALYSER.getNodePosition(nodeIndex));
			node.setType(scotlandJadeEdge.getType());
			nodes.add(node);
		}
		
		return nodes;
	}


	/**
	 * gibt aus der OpenList das Node mit dem niedrigsten F(Weg) Kosten zurück.
	 * @param list
	 * @return
	 */
	private Node getLowestCostNode(List<Node> list) {
		if(list.size() > 0) {
			Node lowestCostNode = list.get(0);
			for(int i = 1; i < list.size(); i++) {
				if(list.get(i)._F < lowestCostNode._F) {
					lowestCostNode = list.get(i);
				}
			}
			return lowestCostNode;
		}
		return null;
	}
	
	/**
	 * prüft ob sich das Node schon in der OpenList befindet. Wenn ja gibt er den zurück
	 * @param node
	 * @param list
	 * @return
	 */
	private Node isOnList(Node node, List<Node> list) {
		for(int i = 0; i < list.size(); i++) {
			if(node._index == list.get(i)._index) {
				return list.get(i);
			}
		}
		return null;
	}
	
	/**
	 * backTrace-Verfahren um den Pfad vom Endnode zum StartNode zu bekommen.
	 * @param endNode
	 * @return
	 */
	private List<Node> getBackTracePath(Node endNode) {
		List<Node> path = new ArrayList<Node>();
		path.add(endNode);
		
		Node currentNode = endNode;
		
		while(currentNode._parent != null) {
			path.add(currentNode._parent);
			currentNode = currentNode._parent;
		}
		return path;
	}
	
	
}
